package com.mybatis.boost.mapper;

import com.mybatis.boost.extend.exception.NoGeneratorKeysException;
import com.mybatis.boost.extend.exception.NoResultException;
import com.mybatis.boost.mapper.model.Condition;
import com.mybatis.boost.mapper.provider.SingleSqlProvider;
import com.mybatis.boost.domain.page.Page;
import com.mybatis.boost.domain.page.PageImpl;
import com.mybatis.boost.domain.page.PageRequest;
import com.mybatis.boost.domain.page.Pageable;
import org.apache.ibatis.annotations.*;
import org.apache.ibatis.exceptions.TooManyResultsException;

import javax.annotation.MatchesPattern;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.validation.constraints.Min;
import java.util.List;
import java.util.Optional;

import static com.mybatis.boost.mapper.model.Condition.empty;
import static com.mybatis.boost.mapper.util.EntityHelperExt.getPKValue;
import static com.mybatis.boost.domain.page.PageUtil.getFirstPageNumber;
import static com.mybatis.boost.domain.page.PageUtil.getLastPageNumber;
import static com.mybatis.boost.domain.page.PageUtil.getOffset;

/**
 * 单表通用CRUD
 *
 * @author jearton
 * @since 2017/2/14
 */
public interface SingleBaseMapper<E, PK> {

    String PARAM_DEFAULT = "_parameter";
    String PARAM_FIELD = "field";
    String PARAM_VALUE = "value";
    String PARAM_CONDITION = "condition";
    String PARAM_RECORD = "record";
    String PARAM_ALLOW_UPDATE_TO_NULL = "allowUpdateToNull";

    /*------------------------------------查------------------------------------*/

    default List<E> selectAll() {
        return select(empty());
    }

    /**
     * 根据主键条件查询唯一记录
     *
     * @param id 主键值
     * @throws NoResultException if no result found
     */
    @Nonnull
    default E selectById(@Nonnull PK id) {
        return selectOptionalById(id).orElseThrow(() -> new NoResultException("entity not found, id = " + id));
    }

    /**
     * 根据主键条件查询唯一记录
     *
     * @param id 主键值
     */
    @SelectProvider(type = SingleSqlProvider.class, method = "dynamicSQL")
    Optional<E> selectOptionalById(@Nonnull PK id);

    /**
     * 根据唯一性条件查询唯一记录
     *
     * @param condition 唯一性条件
     * @throws NoResultException       if no result found
     * @throws TooManyResultsException if more than one result found
     */
    @Nonnull
    default E selectOne(@Nonnull Condition condition) {
        return selectOptional(condition)
                .orElseThrow(() -> new NoResultException("entity not found, condition = " + condition.toInfo()));
    }

    /**
     * 根据唯一性条件查询唯一记录
     *
     * @param condition 唯一性条件
     * @throws TooManyResultsException if more than one result found
     */
    @SelectProvider(type = SingleSqlProvider.class, method = "dynamicSQL")
    Optional<E> selectOptional(@Nonnull Condition condition);

    /**
     * 单字段条件查询（field = value），返回唯一记录
     *
     * @param field 字段名（实体属性名或数据库列名皆可，不区分大小写），例：orderNo，orderno，order_no，`order_no`，ORDER_NO
     * @param value 字段值
     * @throws NoResultException       if no result found
     * @throws TooManyResultsException if more than one result found
     */
    @Nonnull
    @ParametersAreNonnullByDefault
    default E selectOneByField(@MatchesPattern(".+") String field, Object value) {
        return selectOptionalByField(field, value)
                .orElseThrow(() -> new NoResultException("entity not found, field = " + field + ", value = " + value));
    }

    /**
     * 单字段条件查询（field = value），返回唯一记录
     *
     * @param field 字段名（实体属性名或数据库列名皆可，不区分大小写），例：orderNo，orderno，order_no，`order_no`，ORDER_NO
     * @param value 字段值
     * @throws TooManyResultsException if more than one result found
     */
    @ParametersAreNonnullByDefault
    @SelectProvider(type = SingleSqlProvider.class, method = "dynamicSQL")
    Optional<E> selectOptionalByField(@MatchesPattern(".+") @Param(PARAM_FIELD) String field, @Param(PARAM_VALUE) Object value);

    /**
     * 查询前n条记录
     *
     * @param condition 查询条件
     * @param limit     查询数量
     */
    default List<E> selectTop(@Nonnull Condition condition, @Min(1) int limit) {
        return selectByOffset(condition, 0, limit);
    }

    /**
     * 分页查询
     * <p>
     * 注：pageable和condition中的排序参数会合并
     *
     * @param condition 查询条件
     * @param page      分页排序相关条件
     * @see PageRequest
     */
    default Page<E> selectByPage(Condition condition, Pageable page) {
        if (condition == null || page == null) {
            throw new IllegalArgumentException("any parameter must not be null");
        }
        condition.orderBy(page.getSort());
        int offset = page.getOffset();
        int pageSize = page.getPageSize();

        // 分页
        if (!page.isQueryCount()) {
            List<E> contents = selectByOffset(condition, offset, pageSize);
            return new PageImpl<>(contents, page);
        }

        // count + 分页
        long total = count(condition);
        if (!page.isFixEdge() || offset < total) {
            List<E> contents = selectByOffset(condition, offset, pageSize);
            return new PageImpl<>(contents, page, total);
        }

        // count + 分页 + 分页参数修正
        page = page.jumpTo(getLastPageNumber(total, pageSize));
        List<E> contents = selectByOffset(condition, page.getOffset(), pageSize);
        return new PageImpl<>(contents, page, total);
    }

    /**
     * 分页查询（无count查询）
     *
     * @param condition 查询条件
     * @param page      页码（从1开始，如需要从0开始，子接口可以重写）
     * @param size      每页返回数量
     */
    default List<E> selectByPage(@Nonnull Condition condition, int page, int size) {
        return selectByOffset(condition, getOffset(getFirstPageNumber(), page, size), size);
    }

    /**
     * 偏移查询
     *
     * @param condition 查询条件
     * @param offset    偏移量
     * @param size      查询数量
     */
    default List<E> selectByOffset(Condition condition, @Min(0) int offset, @Min(1) int size) {
        if (condition == null) {
            throw new IllegalArgumentException("condition must not be null");
        }
        return select(condition.limit(offset, size));
    }

    /**
     * 单字段条件通用查询
     *
     * @param field 字段名（实体属性名或数据库列名皆可，不区分大小写），例：orderNo，orderno，order_no，`order_no`，ORDER_NO
     * @param value 字段值
     */
    @ParametersAreNonnullByDefault
    @SelectProvider(type = SingleSqlProvider.class, method = "dynamicSQL")
    List<E> selectByField(@MatchesPattern(".+") @Param(PARAM_FIELD) String field, @Param(PARAM_VALUE) Object value);

    /**
     * 单表通用查询
     *
     * @param condition 查询条件
     */
    @SelectProvider(type = SingleSqlProvider.class, method = "dynamicSQL")
    List<E> select(@Nonnull Condition condition);

    /**
     * 查询总记录数
     */
    default long countAll() {
        return count(empty());
    }

    /**
     * 查询记录数
     *
     * @param condition 查询条件
     */
    @SelectProvider(type = SingleSqlProvider.class, method = "dynamicSQL")
    long count(@Nonnull Condition condition);

    /**
     * 根据单字段查询条件统计记录数
     *
     * @param field 字段名（实体属性名或数据库列名皆可，不区分大小写），例：orderNo，orderno，order_no，`order_no`，ORDER_NO
     * @param value 字段值
     */
    @ParametersAreNonnullByDefault
    @SelectProvider(type = SingleSqlProvider.class, method = "dynamicSQL")
    long countByField(@MatchesPattern(".+") @Param(PARAM_FIELD) String field, @Param(PARAM_VALUE) Object value);

    /**
     * 判断记录是否存在
     *
     * @param condition 查询条件
     * @return true if entity exists
     */
    default boolean exists(@Nonnull Condition condition) {
        return count(condition) > 0;
    }

    /**
     * 根据单字段查询条件判断记录是否存在
     *
     * @param field 字段名（实体属性名或数据库列名皆可，不区分大小写），例：orderNo，orderno，order_no，`order_no`，ORDER_NO
     * @param value 字段值
     * @return true if entity exists
     */
    @ParametersAreNonnullByDefault
    default boolean existsByField(@MatchesPattern(".+") String field, Object value) {
        return countByField(field, value) > 0;
    }

    /**
     * 判断记录是否不存在
     *
     * @param condition 查询条件
     * @return true if entity not exists
     */
    default boolean notExists(@Nonnull Condition condition) {
        return count(condition) == 0;
    }

    /**
     * 根据单字段查询条件判断记录是否不存在
     *
     * @param field 字段名（实体属性名或数据库列名皆可，不区分大小写），例：orderNo，orderno，order_no，`order_no`，ORDER_NO
     * @param value 字段值
     * @return true if entity not exists
     */
    @ParametersAreNonnullByDefault
    default boolean notExistsByField(@MatchesPattern(".+") String field, Object value) {
        return countByField(field, value) == 0;
    }

    /*------------------------------------增------------------------------------*/

    /**
     * 新增一条记录
     * <p>
     * 注：
     * 1.如需支持主键回写，则必须在主键字段上配置@GeneratedValue(generator="JDBC")或@GeneratedValue(strategy=IDENTITY)
     * 2.为什么返回void？因为如果新增成功，则影响行数一定为1；如果新增失败，则已经抛异常，不可能返回影响行数为0
     */
    @InsertProvider(type = SingleSqlProvider.class, method = "dynamicSQL")
    void insert(@Nonnull E record);

    /**
     * 新增记录并返回主键值（若抛出异常，则只有2种情况：insert异常 或 主键没有回写）
     * <p>
     * 注：
     * 1.不支持联合主键；
     * 2.对于自增长主键必须配置主键回写
     *
     * @throws NoGeneratorKeysException if pk value not write back
     */
    @Nonnull
    default PK insertAndReturnId(@Nonnull E record) {
        insert(record);
        PK pkValue = getPKValue(record);
        if (pkValue != null) {
            return pkValue;
        }
        throw new NoGeneratorKeysException("check config to ensure pk value could write back, entityClass = " + record.getClass());
    }

    /**
     * 新增记录并返回这条记录的完整信息（若抛出异常，则只有2种情况：第一步insert异常 或 主键没有回写）
     * <p>
     * 注：
     * 1.不支持联合主键；
     * 2.对于自增长主键必须配置主键回写
     * 3.为保证原子性，调用前最好声明事务
     *
     * @throws NoGeneratorKeysException if pk value not write back
     */
    @Nonnull
    default E insertAndReturn(@Nonnull E record) {
        insert(record);
        PK pkValue = getPKValue(record);
        if (pkValue != null) {
            return selectById(pkValue);
        }
        throw new NoGeneratorKeysException("check config to ensure pk value could write back, entityClass = " + record.getClass());
    }

    @InsertProvider(type = SingleSqlProvider.class, method = "dynamicSQL")
    int insertIgnore(@Nonnull E record);

    @InsertProvider(type = SingleSqlProvider.class, method = "dynamicSQL")
    int insertOnDuplicateKeyUpdate(@Nonnull E record);

    /**
     * 该接口限制：
     * 1.若数据库不支持自增长主键，则无法回写主键值，但可以正常insert，如Oracle；
     * 2.不支持读取动态表名
     */
    @InsertProvider(type = SingleSqlProvider.class, method = "dynamicSQL")
    int insertBatch(List<E> recordList);

    /**
     * 该接口限制：
     * 1.若数据库不支持自增长主键，则无法回写主键值，但可以正常insert，如Oracle；
     * 2.不支持读取动态表名
     */
    @InsertProvider(type = SingleSqlProvider.class, method = "dynamicSQL")
    int insertBatchIgnore(List<E> recordList);

    /*------------------------------------改------------------------------------*/

    /**
     * 根据主键条件更新一条记录，并返回更新后的记录（只更新非null属性）
     * <p>
     * 注：若可能出现多处地方同时更新该记录，为避免脏读，调用前最好声明事务
     *
     * @param record 设置更新后的值+主键值（必须设置主键值，否则拒绝执行更新）
     * @return 更新后的记录
     * @throws IllegalArgumentException if id is null
     */
    @Nonnull
    default E updateByIdAndReturn(@Nonnull E record) {
        updateById(record);
        return selectById(getPKValue(record));
    }

    /**
     * 根据主键条件更新一条记录（只更新非null属性）
     * <pre>
     *     class User {
     *         Long id;         // PK
     *         String name;     // NOT NULL
     *         String nickName; // NULL
     *         Integer age;     // NOT NULL
     *     }
     *     // UPDATE `user` SET `age` = 20 WHERE `id` = 123;
     *     updateById(new User().age(20).id(123L));
     * </pre>
     *
     * @param record 设置更新后的值+主键值（只更新非null属性）
     * @return 数据库受影响行数
     * @throws IllegalArgumentException if id is null
     */
    default int updateById(@Nonnull E record) {
        return updateById(record, false);
    }

    /**
     * 根据主键条件更新一条记录
     * <pre>
     *     class User {
     *         Long id;         // PK
     *         String name;     // NOT NULL
     *         String nickName; // NULL
     *         Integer age;     // NOT NULL
     *     }
     *     // UPDATE `user` SET `age` = 20, `nick_name` = null WHERE `id` = 123;
     *     updateById(new User().age(20).id(123L), true);
     * </pre>
     *
     * @param record 更新后的数据（必须设置主键值，否则拒绝执行更新）
     * @return 数据库受影响行数
     * @throws IllegalArgumentException if id is null
     */
    @UpdateProvider(type = SingleSqlProvider.class, method = "dynamicSQL")
    int updateById(@Nonnull @Param(PARAM_RECORD) E record, @Param(PARAM_ALLOW_UPDATE_TO_NULL) boolean allowUpdateToNull);

    /**
     * 单表通用更新（只更新非null属性）
     * <pre>
     *     class User {
     *         Long id;         // PK
     *         String name;     // NOT NULL
     *         String nickName; // NULL
     *         Integer age;     // NOT NULL
     *     }
     *     // UPDATE `user` SET `age` = 20, `nick_name` = null WHERE `name` = "tom" AND `age` < 18;
     *     update(new User().age(20), Condition.form(User.class).andEqualTo("name", "tom").andLessThan("age", 18));
     * </pre>
     *
     * @param record    更新后的数据
     * @param condition 更新条件
     * @return 数据库受影响行数
     */
    @ParametersAreNonnullByDefault
    default int update(E record, Condition condition) {
        return update(record, condition, false);
    }

    /**
     * 单表通用更新
     * <pre>
     *     class User {
     *         Long id;         // PK
     *         String name;     // NOT NULL
     *         String nickName; // NULL
     *         Integer age;     // NOT NULL
     *     }
     *     // UPDATE `user` SET `age` = 20, `nick_name` = "tom2017" WHERE `name` = "tom" AND `age` < 18;
     *     User user = new User().age(20).nickName("tom2017");
     *     updateSelective(user, Condition.form(User.class).andEqualTo("name", "tom").andLessThan("age", 18));
     * </pre>
     *
     * @param record    更新后的数据
     * @param condition 更新条件
     * @return 数据库受影响行数
     */
    @ParametersAreNonnullByDefault
    @UpdateProvider(type = SingleSqlProvider.class, method = "dynamicSQL")
    int update(@Param(PARAM_RECORD) E record, @Param(PARAM_CONDITION) Condition condition, @Param(PARAM_ALLOW_UPDATE_TO_NULL) boolean allowUpdateToNull);

    /**
     * 更新单个字段
     * <pre>
     *     // UPDATE `user` SET `age` = 20 WHERE `name` = "tom" AND `age` < 18;
     *     updateField("age", 20, Condition.form(User.class).andEqualTo("name", "tom").andLessThan("age", 18));
     * </pre>
     *
     * @param field     字段名（实体属性名或数据库列名皆可，不区分大小写），例：orderNo，orderno，order_no，`order_no`，ORDER_NO
     * @param value     更新后的值
     * @param condition 更新条件
     * @return 数据库受影响行数
     */
    @ParametersAreNonnullByDefault
    @UpdateProvider(type = SingleSqlProvider.class, method = "dynamicSQL")
    int updateField(@MatchesPattern(".+") @Param(PARAM_FIELD) String field, @Param(PARAM_VALUE) Object value, @Param(PARAM_CONDITION) Condition condition);

    /*------------------------------------删------------------------------------*/

    /**
     * 根据主键条件删除记录
     * <pre>
     *     // DELETE FROM `user` WHERE `id` = 123;
     *     deleteById(123);
     * </pre>
     *
     * @param id 主键值
     * @return 数据库受影响行数
     */
    @DeleteProvider(type = SingleSqlProvider.class, method = "dynamicSQL")
    int deleteById(@Nonnull PK id);

    /**
     * 根据指定条件删除记录
     * <pre>
     *     // DELETE FROM `user` WHERE `name` = "tom" AND `age` < 18;
     *     delete(Condition.from(User.class).andEqualTo("name", "tom").andLessThan("age", 18));
     * </pre>
     *
     * @param condition 条件
     * @return 数据库受影响行数
     */
    @DeleteProvider(type = SingleSqlProvider.class, method = "dynamicSQL")
    int delete(@Nonnull Condition condition);

    /**
     * 根据指定的单字段条件删除记录
     * <pre>
     *     // DELETE FROM `user` WHERE `name` = "tom";
     *     deleteByField("name", "tom");
     * </pre>
     *
     * @param field 字段名（实体属性名或数据库列名皆可，不区分大小写），例：orderNo，orderno，order_no，`order_no`，ORDER_NO
     * @param value 字段值
     * @return 数据库受影响行数
     */
    @ParametersAreNonnullByDefault
    @DeleteProvider(type = SingleSqlProvider.class, method = "dynamicSQL")
    int deleteByField(@MatchesPattern(".+") @Param(PARAM_FIELD) String field, @Param(PARAM_VALUE) Object value);
}
